今天來點資料庫正規化與反正規化!
正規化通常分為不同的正規化級別,通常以正規形(NF)表示,如下:
每個資料表的每個欄位都應該是不可再分的,也就是說,每個欄位應該包含單一的數值。
如果有一個訂單的資料表,應該將訂單的產品列表拆分成獨立的資料表,而不是將他們存儲在一個單一的欄位中。
未正規化的訂單資料表
訂單ID | 顧客名稱 | 產品列表 | 價格 |
---|---|---|---|
1 | Viii | 商品A, 商品B, 商品C | 200, 150, 100 |
2 | Mark | 商品B, 商品D | 150, 50 |
為了符合第一正規化,我們應該將資料庫修改如下,
沒有任何兩筆以上的資料是完全重複且資料表中有 Primary Key,
而其他所有的欄位都相依於 Primary Key
第一正規化的訂單資料表
訂單ID | 顧客名稱 | 產品列表 | 價格 |
---|---|---|---|
1 | Viii | 商品A | 200 |
1 | Viii | 商品B | 150 |
1 | Viii | 商品C | 100 |
2 | Mark | 商品B | 150 |
2 | Mark | 商品D | 50 |
一個資料表應該符合第一正規形也就是要符合: 1NF,
並且非主鍵欄位應該完全依賴於主鍵。
延續剛剛的訂單資料表,主鍵是訂單 ID,並且訂單中包含多個產品,
則應該將產品的資訊拆分成另一個資料表,
其中主鍵包括訂單 ID 和產品 ID,以確保非主鍵欄位只依賴於主鍵。
延續第一正規化的訂單資料表
訂單ID | 顧客名稱 | 產品列表 | 價格 |
---|---|---|---|
1 | Viii | 商品A | 200 |
1 | Viii | 商品B | 150 |
1 | Viii | 商品C | 100 |
2 | Mark | 商品B | 150 |
2 | Mark | 商品D | 50 |
為了符合第二正規化,我們應該將產品的資訊拆分成另一個資料表,
並使用複合主鍵(訂單 ID 和產品 ID)建立 "訂單產品" 資料表。
建立 "訂單" 資料表,包含訂單相關的信息,並使用訂單 ID 作為主鍵。
訂單資料表 (Order)
訂單ID | 顧客名稱 |
---|
1 | Viii |
2 | Mark |
建立 "產品" 資料表,包含產品相關的信息,並使用產品 ID 作為主鍵。
產品資料表 (Product)
產品ID | 產品名稱 | 價格 |
---|
101 | 商品A | 200 |
102 | 商品B | 150 |
103 | 商品C | 100 |
104 | 商品D | 50 |
建立 "訂單產品" 資料表,用於建立 "訂單" 和 "產品" 之間的關聯。
在這個資料表中,使用複合主鍵(訂單 ID 和產品 ID)作為主鍵。
訂單產品資料表 (OrderProduct)
訂單ID | 產品ID | 價格 |
---|
1 | 101 | 200 |
1 | 102 | 150 |
1 | 103 | 100 |
2 | 102 | 150 |
2 | 104 | 50 |
3 | 101 | 200 |
3 | 103 | 100 |
一個資料表應該符合第二正規形也就是要符合: 2NF,並且非主鍵欄位不應該相互依賴。
如果有一個包含顧客地址的資料表,應該將這些資訊拆分成不同的資料表,
以確保每個欄位都只依賴於主鍵。
假設有一個未正規化的訂單資料表如下:
未正規化的訂單資料表
訂單ID | 顧客名稱 | 顧客地址 | 產品ID | 產品名稱 | 價格 |
---|---|---|---|---|---|
1 | Viii | 220 自由市 | 101 | 商品A | 200 |
1 | Viii | 220 自由市 | 102 | 商品B | 150 |
2 | Mark | 300 勝利市 | 101 | 商品A | 200 |
將其轉為第三正規化
建立 "顧客" 資料表,包含顧客相關的信息,並使用顧客 ID 作為主鍵。
顧客資料表 (Customer)
顧客ID | 顧客名稱 | 顧客地址 |
---|
1 | Viii | 220 自由市 |
2 | Mark | 300 勝利市 |
建立 "產品" 資料表,包含產品相關的信息,並使用產品 ID 作為主鍵。
產品資料表 (Product)
產品ID | 產品名稱 | 價格 |
---|
101 | 商品A | 200 |
102 | 商品B | 150 |
建立 "訂單" 資料表,包含訂單相關的信息,並使用訂單 ID 作為主鍵,同時包含顧客 ID 和產品 ID 作為外鍵,以建立與 "顧客" 和 "產品" 資料表之間的關聯。
訂單資料表 (Order)
訂單ID | 顧客ID | 產品ID |
---|
1 | 1 | 101 |
1 | 1 | 102 |
2 | 2 | 101 |
來總結一下為什麼我們需要正規化資料庫吧!
正規化有助於:
緊接著,說到資料庫正規化,是不是也有反正規化?
:有!讓我們來看看吧!
資料庫反正規化(Database Denormalization)是一種在關聯式資料庫中的設計過程,
其目的是為了提高查詢效能或滿足特定應用需求而故意違反正規化原則,
將資料庫中的表格結構先遵守正規化的所有規則,再進行局部調整,故意打破一些正規化規則!
之前有提過,正規化通常是資料庫設計的一個關鍵原則,旨在減少重複數據,提高資料完整性,
但有時候,為了達到更好的查詢效能或簡化某些操作,反正規化可以成為一種有用的策略。
正規化的目標是將數據存在多個表格中,以減少數據的重複性;
而反正規化則是將數據冗餘性增加,把部分數據在不同地方多存幾份,加快數據檢索速度,
犧牲一部分的寫入性能,換取更高的讀取性能。
透過簡單的例子來看看:
正規化表格
假設我們正在設計一個簡單的訂單追蹤系統,我們可以建立以下兩個有正規化的表格,
將客戶資訊存在獨立的表格中,並通過客戶 ID 關聯到訂單。
顧客(Customers):
客戶ID (CustomerID) | 姓名 (Name) | 電子郵件 (Email) |
---|
1 | Viii | viii@example.com |
2 | Mark | mark@example.com |
訂單(Orders):
訂單ID (OrderID) | 客戶ID (CustomerID) | 日期 (Date) |
---|
101 | 1 | 2023-10-01 |
102 | 2 | 2023-10-02 |
反正規化表格
假設我們經常需要在查詢中獲得顧客姓名與他們的訂單資訊,
正規化的設計可能會需要多個 JOIN 操作,會導致查詢效能問題。
在這種情況下,我們可以採用反正規化的方法,只建立一個包含冗餘數據的表格:
將顧客姓名和電子郵件冗餘地存儲在訂單表格中,這樣我們在查詢時可以更快速地檢索顧客信息,
但同時也增加了數據的冗餘性。
反正規化的訂單表格(Denormalized Orders):
訂單ID (OrderID) | 客戶ID (CustomerID) | 顧客名稱 (CustomerName) | 日期 (Date) | 電子郵件 (Email) |
---|
101 | 1 | Viii | 2023-10-01 | viii@example.com |
102 | 2 | Mark | 2023-10-02 | mark@example.com |
透過先前提過的正規化我們可以統整一下兩者的差異:
特徵 | 正規化 | 反正規化 |
---|---|---|
數據冗餘性 | 減少冗餘性,每個數據僅存儲一次。 | 增加冗餘性,數據在多個位置存儲。 |
表格數量 | 使用多個表格來避免重複數據存儲。 | 使用較少的表格,合併數據以提高查詢效能。 |
數據完整性 | 通過外鍵約束確保數據的完整性。 | 數據完整性可能較難維護。 |
查詢效能 | 複雜查詢需要多個 JOIN 操作,效能較慢。 | 簡化查詢,提高效能。 |
空間需求 | 通常用較少的存儲空間。 | 可能需要更多存儲空間。 |
數據一致性 | 通常有較高的數據一致性。 | 數據一致性可能較難維護。 |
設計複雜度 | 較少冗餘,複雜性通常較低。 | 高冗餘情況下可能需要更複雜的設計。 |
更新/插入/刪除操作 | 較簡單,因為每個數據元素僅存儲一次。 | 較複雜,因為需要同時更新多個位置的數據。 |
透過整理兩者之間的差異之後,對於要使用正規化還是反正規化可以從自身的需求去判斷。
如果是需要嚴謹要求數據一致性、資料更新頻繁、需要節省空間,可能正規化是好的設計策略;
若是遇到需要簡化查詢、快速讀取資料或許就可以選擇反正規化了!
總之,正規化和反正規化都是有用的工具,但應根據具體的應用需求和性能目標來選擇。有時候,正確的做法可能是在同一個資料庫中同時使用正規化和反正規化,以滿足不同的需求。最重要的是在設計資料庫時深入理解應用需求,並謹慎權衡正規化和反正規化的利弊。
今天就到這啦!下篇見~!
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)